/*
 * AllwinnerTech hdmi cec driver
 *
 * Copyright (C) 2015 AllwinnerTech Corporation
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <linux/slab.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/ktime.h>

#include "hdmi_cec.h"

struct frame_transmit_context {
	int state;
	int error;
	int complete;

	unsigned long long irq_timestamp;
	struct hrtimer timer;
	ktime_t alarm;
	int restart;

	struct hdmi_cec_frame *frame;

	/* for busy detect */
	int busy_det_cnt;
	int busy_detect;

	/* for block transmit */
	int bit_transmit_state;
	int bit_to_transmit;
	int block_byte;
	int block_eom;
	int block_ack_expect;
	int block_ack_actual;
	int operand_index;

	struct mutex lock;
	wait_queue_head_t wq;
};

extern int debug_mask;
static struct frame_transmit_context receive_frame;
static struct frame_transmit_context send_frame;
#define _transmit_exclude_bit	(0x1)
static volatile unsigned long transmit_exclude = 0;

const char *errstr(int errno);
static enum hrtimer_restart
send_frame_timer_handler(struct hrtimer *timer);
static enum hrtimer_restart
receive_frame_timer_handler(struct hrtimer *timer);

int hdmi_cec_utils_init(void)
{
	/* init send frame context */
	memset(&send_frame, 0, sizeof(send_frame));
	init_waitqueue_head(&send_frame.wq);
	mutex_init(&send_frame.lock);
	hrtimer_init(&send_frame.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	send_frame.timer.function = send_frame_timer_handler;
	send_frame.state = SEND_FRAME_IDLE;
	send_frame.error = CEC_TRANSMIT_OK;
	send_frame.complete = 0;
	send_frame.busy_det_cnt = CEC_DEFAULT_PERIOD;
	send_frame.busy_detect = CEC_DEFAULT_PERIOD;

	/* init receive frame context */
	memset(&receive_frame, 0, sizeof(receive_frame));
	init_waitqueue_head(&receive_frame.wq);
	mutex_init(&receive_frame.lock);
	hrtimer_init(&receive_frame.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	send_frame.timer.function = receive_frame_timer_handler;
	send_frame.state = RECV_FRAME_IDLE;
	send_frame.error = CEC_TRANSMIT_OK;
	send_frame.complete = 1;

	return 0;
}

int hdmi_cec_utils_exit(void)
{
	hrtimer_cancel(&send_frame.timer);
	hrtimer_cancel(&receive_frame.timer);
	return 0;
}

static int disable_frame_receive(void)
{
	if (test_and_set_bit(_transmit_exclude_bit, &transmit_exclude) == 0) {
		cec_line_irq_disable();
		config_cec_line_as_output();
		return 0;
	}
	cec_debug("warning: receive frame is pending\n");
	return -1;
}

static int enable_frame_receive(void)
{
	clear_bit(_transmit_exclude_bit, &transmit_exclude);
	config_cec_line_as_input();
	cec_line_irq_enable();
	return 0;
}

int hdmi_cec_send_frame(const struct hdmi_cec_frame *frame)
{
	if (!mutex_trylock(&send_frame.lock)) {
		cec_debug("warning, send frame is pending\n");
		return -1;
	}
	if (disable_frame_receive() != 0) {
		send_frame.error = CEC_TRANSMIT_LINE_BUSY;
		goto transmit_busy;
	}

	send_frame.frame = (struct hdmi_cec_frame *)frame;
	send_frame.alarm = ktime_set(0, CEC_PER_SEND_FRAME);
	hrtimer_start(&send_frame.timer, send_frame.alarm, HRTIMER_MODE_REL);
	wait_event(send_frame.wq, (send_frame.complete == 1));
	send_frame.complete = 0;
	send_frame.busy_det_cnt = CEC_DEFAULT_PERIOD;
	send_frame.busy_detect = CEC_DEFAULT_PERIOD;

	enable_frame_receive();
transmit_busy:
	mutex_unlock(&send_frame.lock);
	return send_frame.error;
}

enum send_frame_state busy_detect(struct frame_transmit_context *ctx)
{
	enum send_frame_state ret = SEND_FRAME_BUSY_DET;
	if (get_cec_line_state()) {
		ctx->busy_detect--;
		if (ctx->busy_detect < 0) {
			set_cec_line_state(0);
			ctx->alarm = ktime_set(0, CEC_START_BIT_LOW_TIME);
			ret = SEND_FRAME_START_BIT;
		}
	} else {
		ctx->error = CEC_TRANSMIT_LINE_BUSY;
		ret = SEND_FRAME_END;
	}

	return ret;
}

enum send_frame_state send_start_bit(struct frame_transmit_context *ctx)
{
	set_cec_line_state(1);
	ctx->alarm = ktime_set(0, CEC_START_BIT_HIGH_TIME);
	return SEND_FRAME_HEADER_BLOCK;
}

/* return define:
 * <0: error
 *  0: block transmit is running
 *  1: block transmit is over
 * */
int send_one_block(struct frame_transmit_context *ctx)
{
	int bit = 0;
	int ret = 0;

	if (ctx->bit_to_transmit) {
		switch (ctx->bit_transmit_state) {

		case CEC_BIT_TR_PRE:
			if (ctx->bit_to_transmit) {
				ctx->bit_to_transmit--;
				bit = (ctx->block_byte >> ctx->bit_to_transmit) & 0x01;
			} else if (ctx->block_eom != CEC_EOM_BLOCK_INVALID) {
				/* prepare to send eom bit */
				bit = ctx->block_eom;
				ctx->block_eom = CEC_EOM_BLOCK_INVALID;
			} else {
				/* prepare for ack */
				ctx->bit_transmit_state = CEC_BIT_TR_ACK;
				set_cec_line_state(0);
				ctx->alarm =  ktime_set(0, CEC_LOGIC_1_LOW_TIME);
				break;
			}
		case CEC_BIT_TR_LOW:
			ctx->bit_transmit_state = CEC_BIT_TR_HIGH;
			set_cec_line_state(0);
			ctx->alarm =
				ktime_set(0, (bit ? CEC_LOGIC_1_LOW_TIME:CEC_LOGIC_0_LOW_TIME));
			break;
		case CEC_BIT_TR_HIGH:
			ctx->bit_transmit_state = CEC_BIT_TR_PRE;
			set_cec_line_state(1);
			ctx->alarm =
				ktime_set(0, (bit ? CEC_LOGIC_1_HIGH_TIME:CEC_LOGIC_0_HIGH_TIME));
			break;
		case CEC_BIT_TR_ACK:
			ctx->bit_transmit_state = CEC_BIT_TR_ACK_SAMPLE;
			set_cec_line_state(1);
			ctx->alarm = ktime_set(0, CEC_BYTE_ACK_SAMPLE_TIME);
			break;
		case CEC_BIT_TR_ACK_SAMPLE:
			ctx->bit_transmit_state = CEC_BIT_TR_PRE;
			ctx->block_ack_actual = get_cec_line_state();
			ret = (ctx->block_ack_actual==ctx->block_ack_expect)?(1):(-1);
			ctx->alarm = ktime_set(0, CEC_BYTE_ACK_COMPLETE_TIME);
			break;
		default:
			ctx->error = -1;
			ret = -1;
			break;
		}
	}

	return ret;
}

enum send_frame_state send_header_block(struct frame_transmit_context *ctx)
{
	int state = 0;
	int ret = send_one_block(ctx);

	switch (ret) {
	case 0:
		state = SEND_FRAME_HEADER_BLOCK;
		break;
	case 1:
		state = SEND_FRAME_DATA_BLOCK_PRE;
		break;
	default:
		state = SEND_FRAME_END;
		ctx->error = CEC_TRANSMIT_ACK_ERROR;
		break;
	}
	return state;
}

enum send_frame_state send_header_block_prepare(struct frame_transmit_context *ctx)
{
	const struct hdmi_cec_frame *frame = ctx->frame;
	ctx->operand_index = 0;
	ctx->bit_to_transmit = 8;
	ctx->block_byte = CEC_MAKE_HEADER(frame->initiator, frame->destination);
	ctx->block_eom =
		(frame->operand_num > 0) ? CEC_EOM_BLOCK_MORE : CEC_EOM_BLOCK_END;
	ctx->bit_transmit_state = CEC_BIT_TR_PRE;
	return SEND_FRAME_HEADER_BLOCK;
}

enum send_frame_state send_data_block_prepare(struct frame_transmit_context *ctx)
{
	const struct hdmi_cec_frame *frame = ctx->frame;
	if (ctx->operand_index++ < frame->operand_num) {
		ctx->bit_to_transmit = 8;
		ctx->block_byte = frame->operand[ctx->operand_index];
		ctx->block_eom = (ctx->operand_index == frame->operand_num) ?
							CEC_EOM_BLOCK_END : CEC_EOM_BLOCK_MORE;
		ctx->bit_transmit_state = CEC_BIT_TR_PRE;
		return SEND_FRAME_DATA_BLOCK;
	}
	ctx->error = CEC_TRANSMIT_OK;
	return SEND_FRAME_END;
}

enum send_frame_state send_data_block(struct frame_transmit_context *ctx)
{
	int state = 0;
	int ret = send_one_block(ctx);

	switch (ret) {
	case 0:
		state = SEND_FRAME_DATA_BLOCK;
		break;
	case 1:
		state = SEND_FRAME_DATA_BLOCK_PRE;
		break;
	default:
		state = SEND_FRAME_END;
		ctx->error = CEC_TRANSMIT_ACK_ERROR;
		break;
	}
	return state;
}

static enum hrtimer_restart
send_frame_timer_handler(struct hrtimer *timer)
{
	switch (send_frame.state) {

	case SEND_FRAME_IDLE:
		if (send_frame.frame) {
			send_frame.state = SEND_FRAME_BUSY_DET;
		} else {
			send_frame.error = CEC_TRANSMIT_NO_INPUT;
			send_frame.state = SEND_FRAME_END;
			break;
		}
	case SEND_FRAME_BUSY_DET:
		send_frame.state = busy_detect(&send_frame);
		break;

	case SEND_FRAME_START_BIT:
		send_frame.state = send_start_bit(&send_frame);
		if (send_frame.state == SEND_FRAME_HEADER_BLOCK)
			send_header_block_prepare(&send_frame);
		break;
	
	case SEND_FRAME_HEADER_BLOCK:
		send_frame.state = send_header_block(&send_frame);
		break;

	case SEND_FRAME_DATA_BLOCK_PRE:
		send_frame.state = send_data_block_prepare(&send_frame);
		if (send_frame.state == SEND_FRAME_END) {
			break;
		}
	case SEND_FRAME_DATA_BLOCK:
		send_frame.state = send_data_block(&send_frame);
		break;
	case SEND_FRAME_END:
		send_frame.state = SEND_FRAME_IDLE;
		send_frame.complete = 1;
		wake_up(&send_frame.wq);
		cec_debug("cec transmit end: %s\n", errstr(send_frame.error));
		break;
	}

	if (!send_frame.complete) {
		hrtimer_forward_now(timer, send_frame.alarm);
		return HRTIMER_RESTART;
	}
	return HRTIMER_NORESTART;
}

/* *
 * recive irq handler 
 */

static void receive_frame_start(void);
static void receive_frame_sample_trigger(void);
unsigned int hdmi_cec_line_irq_handler(void *dump)
{
	unsigned long long interval =
			ktime_to_ns(ktime_get()) - receive_frame.irq_timestamp;
	if (interval>CEC_START_BIT_TOTAL_MIN && interval<CEC_START_BIT_TOTAL_MAX) {
		/* new frame receive start */
		receive_frame_start();
	} else {
		/* trigger to sample cec line state */
		receive_frame_sample_trigger();
	}

	receive_frame.irq_timestamp = ktime_to_ns(ktime_get());
	return 0;
}

static void receive_frame_start(void)
{
	set_bit(_transmit_exclude_bit, &transmit_exclude);
	receive_frame.state = RECV_FRAME_IDLE;
	receive_frame.alarm = ktime_set(0, CEC_LOGICAL_BIT_SAMPLE_TIME);
	hrtimer_start(&receive_frame.timer, receive_frame.alarm, HRTIMER_MODE_REL);
}

static void receive_frame_sample_trigger(void)
{
	receive_frame.alarm = ktime_set(0, CEC_LOGICAL_BIT_SAMPLE_TIME);
	hrtimer_start(&receive_frame.timer, receive_frame.alarm, HRTIMER_MODE_REL);
}

static void receive_frame_finish(void)
{
	clear_bit(_transmit_exclude_bit, &transmit_exclude);
	if (receive_frame.error == CEC_TRANSMIT_OK) {
		
	}
	return;
}

static void receive_frame_prepare(struct frame_transmit_context *ctx)
{
	ctx->frame = kzalloc(sizeof(*ctx->frame), GFP_KERNEL);
	ctx->bit_to_transmit = 8;
	ctx->operand_index = 0;
	ctx->block_byte = 0;
	ctx->block_eom = 0;
	ctx->restart = 0;
	ctx->bit_transmit_state = CEC_RECV_BIT_SAMPLE;
}


static void receive_data_block_prepare(struct frame_transmit_context *ctx)
{
	ctx->bit_transmit_state = 8;
	ctx->block_byte = 0;
	ctx->block_eom = 0;
	ctx->restart = 0;
	ctx->bit_transmit_state = CEC_RECV_BIT_SAMPLE;
}

int receive_one_block(struct frame_transmit_context *ctx);
enum receive_frame_state
receive_header_block(struct frame_transmit_context *ctx)
{
	enum receive_frame_state state;
	int ret = receive_one_block(ctx);
	switch (ret) {
	case 0:
		state = RECV_FRAME_HEADER_BLOCK;
		break;
	case 1:
		ctx->error = CEC_TRANSMIT_OK;	
		state = (ctx->block_eom == CEC_EOM_BLOCK_MORE) ?
					RECV_FRAME_DATA_BLOCK_PER : RECV_FRAME_END;
		break;
	case -1:
		state = RECV_FRAME_END;
		break;
	default:
		state = RECV_FRAME_END;
		ctx->error = CEC_TRANSMIT_UNKNOW_ERROR;
		break;
	}
	return state;
}

enum receive_frame_state
receive_data_block(struct frame_transmit_context *ctx)
{
	enum receive_frame_state state;
	int ret = receive_one_block(ctx);
	switch (ret) {
	case 0:
		state = RECV_FRAME_DATA_BLOCK;
		break;
	case 1:
		ctx->frame->operand[ctx->operand_index++] = ctx->block_byte;
		ctx->frame->operand_num = ctx->operand_index;
		ctx->error = CEC_TRANSMIT_OK;	
		state = (ctx->block_eom == CEC_EOM_BLOCK_MORE) ?
					RECV_FRAME_DATA_BLOCK_PER : RECV_FRAME_END;
		break;
	case -1:
		state = RECV_FRAME_END;
		break;
	default:
		state = RECV_FRAME_END;
		ctx->error = CEC_TRANSMIT_UNKNOW_ERROR;
		break;
	}
	return state;
}

static enum hrtimer_restart
receive_frame_timer_handler(struct hrtimer *timer)
{
	switch (receive_frame.state) {
	case RECV_FRAME_IDLE:
		receive_frame_prepare(&receive_frame);
		receive_frame.state = RECV_FRAME_HEADER_BLOCK;
	case RECV_FRAME_HEADER_BLOCK:
		receive_frame.state = receive_header_block(&receive_frame);
		break;
	case RECV_FRAME_DATA_BLOCK_PER:
		receive_data_block_prepare(&receive_frame);
	case RECV_FRAME_DATA_BLOCK:
		receive_frame.state = receive_data_block(&receive_frame);
		break;
	case RECV_FRAME_END:
	default:
		break;
	}

	if (receive_frame.restart) {
		receive_frame.restart = 0;
		hrtimer_forward_now(&receive_frame.timer, receive_frame.alarm);
		return HRTIMER_RESTART;
	}

	if (receive_frame.state == RECV_FRAME_END) {
		receive_frame_finish();
	}
	return HRTIMER_NORESTART;
}

/* return define:
 * <0: error
 *  0: block transmit is running
 *  1: block transmit is over
 * */
int receive_one_block(struct frame_transmit_context *ctx)
{
	int bit = 0;
	int complete = 0;

	switch (ctx->bit_transmit_state) {
	case CEC_RECV_BIT_SAMPLE:
		bit = get_cec_line_state();
		ctx->bit_to_transmit--;
		ctx->block_byte |= (bit << ctx->bit_to_transmit);
		ctx->bit_transmit_state =
			(ctx->bit_to_transmit) ? CEC_RECV_BIT_SAMPLE : CEC_RECV_EOM_SAMPLE;
		break;
	case CEC_RECV_EOM_SAMPLE:
		ctx->block_eom = get_cec_line_state();
		ctx->frame->initiator = CEC_GET_INITIATOR(ctx->block_byte);
		ctx->frame->destination = CEC_GET_DESTINATION(ctx->block_byte);
		if (is_logical_addr_match(ctx->frame->destination)) {
			set_cec_line_state(0);
			ctx->restart = 1;
			ctx->alarm = ktime_set(0, CEC_RECV_ASSERT_ACK_TIME);
			ctx->bit_transmit_state = CEC_RECV_ASSERT_ACK;
		}
		if (is_broadcast(ctx->frame->destination)) {
			complete = 1;
			ctx->error = CEC_TRANSMIT_OK;
		} else {
			complete = -1;
			ctx->error = CEC_TRANSMIT_ADDRESS_ERROR;
		}
		break;
	case CEC_RECV_ASSERT_ACK:
		set_cec_line_state(1);
		complete = 1;
		ctx->error = CEC_TRANSMIT_OK;
		break;
	default:
		complete = -1;
		ctx->error = CEC_TRANSMIT_UNKNOW_ERROR;
		break;
	}
	return complete;
}

const char *errstr(int errno)
{
	return "ERROR UNKNOWN";
}
